异常处理
当一个程序被设计为一集模块以后,对于错误的处理也必须在这些模块的基础之上考虑。哪个模块应该承担对于哪个错误的处理责任?常常遇到的情况是,检查出错误的模块并不知道应该去做些什么,恢复的动作依赖于那些调用操作的模块,而不是那个试图去执行操作并且发现错误的模块。随着程序不断增大,特别是随着各种库的广泛使用,处理错误(或者更一般的,“异常情形”)的标准方式也变得更加重要了。
重新考虑有关Stack的例子。如果我们试图向堆栈中 push()
放进的字符过多,这时应该怎么办呢?写Stack的人不可能知道在这种情况下用户希望做些什么,而用户也未必能够始终如一地检查这个问题(如果真能这样做的话,溢出问题根本就不会出现了)。解决的办法就是让Stack的实现者去检查溢出问题,而后告诉那个(它并不知道是什么的)用户。这样就使用户可以采取适当的行动。例如,
namespace Stack // 界面
{
void push(char);
char pop();
class Overflow{ }; // 表示溢出异常的类型
}
当检查到溢出之时,Stack::push()
可以激活异常处理代码;也就是说,它“抛出一个Overflow异常”:
void Stack::push(char c)
{
if(top == max_size) throw Overflow();
// 压入c
}
这个 throw
将把控制传递给某个处理 Stack::Overflow
异常的处理器,该处理器应该位于某个直接或者间接调用了 Stack::push()
的函数里。在这个传递过程中,实现将根据需要退掉一部分函数调用栈,退回到能处理异常的那个调用函数的环境位置。可见,抛出的行为就像是一种多层的返回。例如,
void f()
{
// ...
try {
while(true) Stack::push('c');
}
catch (Stack::Overflow) {
// 呜呼,出现异常;执行适当动作
}
// ...
}
这里的 while
循环将不断地做下去。当然,在某个对Stack::push()
的调用导致 throw
时,执行就将进入提供了针对 Stack::Overflow
的处理器的 catch
子句之中。
采用异常处理机制能使对错误的处理更加规范,也使它更容易看清楚。
🔚